/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package hos.retrofit;

import static hos.retrofit.Utils.methodError;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * Adapts an invocation of an interface method into an HTTP call.
 */
class RetrofitServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
    /**
     * Inspects the annotations on an interface method to construct a reusable service method that
     * speaks HTTP. This requires potentially-expensive reflection so it is best to build each service
     * method only once and reuse it.
     */
    static <ResponseT, ReturnT> RetrofitServiceMethod<ResponseT, ReturnT> parseAnnotations(
            Retrofit retrofit, Method method, RetrofitRequestFactory retrofitRequest) {
        Annotation[] annotations = method.getAnnotations();
        Type adapterType = method.getGenericReturnType();
        RetrofitCallAdapter<ResponseT, ReturnT> retrofitCallAdapter =
                createCallAdapter(retrofit, method, adapterType, annotations);
        RetrofitCall.Factory retrofitCallFactory = retrofit.getRetrofitCallFactory();
        RetrofitConverter<Object, ResponseT> converter = retrofit.converter(adapterType, annotations);
        return new RetrofitServiceMethod<ResponseT, ReturnT>(retrofit,
                retrofitRequest, retrofitCallFactory,
                retrofitCallAdapter, converter);
    }

    private static <ResponseT, ReturnT> RetrofitCallAdapter<ResponseT, ReturnT> createCallAdapter(
            Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
        try {
            //noinspection unchecked
            return (RetrofitCallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
        } catch (RuntimeException e) { // Wide exception range because factories are user code.
            throw methodError(method, e, "Unable to create call adapter for %s", returnType);
        }
    }

    private final RetrofitRequestFactory requestFactory;
    private final RetrofitCallAdapter<ResponseT, ReturnT> retrofitCallAdapter;
    Retrofit retrofit;
    RetrofitCall.Factory retrofitCallFactory;
    RetrofitConverter<Object, ResponseT> converter;

    RetrofitServiceMethod(
            Retrofit retrofit, RetrofitRequestFactory requestFactory, RetrofitCall.Factory retrofitCallFactory,
            RetrofitCallAdapter<ResponseT, ReturnT> retrofitCallAdapter, RetrofitConverter<Object, ResponseT> converter) {
        this.retrofit = retrofit;
        this.requestFactory = requestFactory;
        this.retrofitCallAdapter = retrofitCallAdapter;
        this.retrofitCallFactory = retrofitCallFactory;
        this.converter = converter;
    }

    @Override
    final ReturnT invoke(Object instance, Object[] args) {
        RetrofitRequest retrofitRequest = RetrofitRequest.parseAnnotations(retrofit, requestFactory, args);
        DefaultRetrofitCall<ResponseT> retrofitCallReal = new DefaultRetrofitCall<>(retrofitRequest, instance, converter, retrofitCallFactory);
        return retrofitCallAdapter.adapt(retrofitCallReal);
    }

}
